package com.jd.platform.sample2.util;

import com.jd.platform.hotkey.client.callback.JdHotKeyStore;
import com.jd.platform.hotkey.client.core.rule.KeyRuleHolder;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author wuweifeng wrote on 2020-02-21
 * @version 1.0
 *
 * 与sample最大的差别：
 * 只将JdHotKeyStore当作是计数器，存储缓存在另一个redis里
 * 只需要改动get和set方法
 */
@Component
public class Cache {
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource(name = "redis2Template")
    private RedisTemplate<String, String> redis2Template;

    //只是普通的获取值
    public String getFromRedis(String key) {
        return redisTemplate.opsForValue().get(key); //获取普通key
    }

    //走JdHotKeyStore计数的获取值
    // JdHotKeyStore是Caffeine为缓存。存到JVM 的内存中了，如果有需要则弄多一个redis的bean代替其进行读写
    // JdHotKeyStore的isHotKey和getValue方法是执行完对应逻辑才计数   ；  get不计数
    // JdHotKeyStore的计数是  不是hotkey才计数 以及 hotkey还有1秒过期才计数  注意：是hotkey时不到最后1秒不计数
    public String get(String key) {
        if (JdHotKeyStore.isHotKey(key)) { //如果已经缓存过了，证明是hotkey  ； 不管是不是hotkey都进行计数
            Object object = redis2Template.opsForValue().get(key); //是hotkey才获取到值
            if (object != null) { //此时redis2Template已经存有值了，不需要从中央redis加载获取
                System.out.println("此时是hotkey");
                return object.toString();
            }
            System.out.println("虽然是hotkey，但没有存值");
        } else {
            System.out.println("此时不是hotkey");
            redis2Template.delete(key); //可省
        }

//        redis2Template.delete(key); //JdHotKeyStore知道不是hotkey，redis2Template需要将其冷删除（但不是hotkey时没人访问就没有删除，积压在这，最好做个定时任务或者设置过期时间）
        String value = redisTemplate.opsForValue().get(key); //获取普通key
        if(value==null) { //该key不存在即没有存值时，被恶意访问了多次，为避免缓存穿透，设置值为""
            value="";
        }
        JdHotKeyStore.smartSet(key, ""); //是热值才赋值为hotkey，不过redis2Template存储实际值，这里只为了存key计数
        if(JdHotKeyStore.get(key)!=null) { //普通的get不是getValue不会触发计数
            // 1. 存储主键（不过期，存实际value）
            redis2Template.opsForValue().set(key, value);

            // 2. 创建影子键（设过期时间，value为空字符串节省空间）
            int time= KeyRuleHolder.duration(key);
            String shadowKey = "pre_" + key;
            redis2Template.opsForValue().set(shadowKey, "", time, TimeUnit.SECONDS);
        }
        return value;
    }

    //这里可能是添加，也可能是更新
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value); //添加只执行了这个，因为一开始不是hotkey；更新也执行这个

        if(JdHotKeyStore.get(key)!=null) { //get方法中的smartSet成功存值说明是hotkey，需要更新
            redis2Template.opsForValue().set(key, value); //影子键无需更新
        }
    }

    //如果可预见的知道某hotkey将很少被访问，则只执行这个
    public void beCold(String key) {
        JdHotKeyStore.remove(key);
    }

//    //如果可预见的知道某hotkey将很多被访问，则执行这个（没必要，JdHotKeyStore的存在就是为了应对突发的高访问）
//    public void beHot(String key, String value) {
//        JdHotKeyStore.forceSet(key,value);
//    }

    //删除redis的数据，此时为避免幻读，需要将JdHotKeyStore中对应的hotkey一起删了
    public void remove(String key) {
        redisTemplate.delete(key);
        JdHotKeyStore.remove(key);
    }
}
